package com.androidtest3d.rendering;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import com.androidtest3d.math.MyVector;
import com.androidtest3d.scene.Node;
import com.androidtest3d.view.Camera;

import android.opengl.GLSurfaceView;
import android.opengl.GLU;
import android.util.Log;

/**
 * Android Game Tutorial Project.
 *
 * Copyright (C) 2011  Philipp Jenke
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * Implements the 3D rendering functionality.
 */
public class Renderer3D implements GLSurfaceView.Renderer {

	/**
	 * Tag used in logging messages.
	 */
	private static final String LOG_TAG = "Android Test 3D";

	/**
	 * Background color.
	 */
	private static final MyVector BACKGROUND_COLOR = MyVector.makeVector4(0.7,
			0.7, 0.7, 1.0);

	/**
	 * Light position.
	 */
	private static final MyVector LIGHT_POSITION = MyVector.makeVector4(-2.0f,
			-1.5f, -2.5f, 1.0f);

	/**
	 * Diffuse light component.
	 */
	private static final MyVector LIGHT_DIFFUSE = MyVector.makeVector4(1.0f,
			1.0f, 1.0f, 1.0f);

	/**
	 * Ambient light component.
	 */
	private static final MyVector LIGHT_AMBIENT = MyVector.makeVector4(1.0f,
			1.0f, 1.0f, 1.0f);

	/**
	 * Diffuse material component.
	 */
	private static final MyVector MATERIAL_DIFFUSE = MyVector.makeVector4(
			0.25f, 0.75f, 0.25f, 1.0f);

	/**
	 * Ambient material component.
	 */
	private static final MyVector MATERIAL_AMBIENT = MyVector.makeVector4(0.1f,
			0.1f, 0.1f, 1.0f);

	/**
	 * Specular material component.
	 */
	private static final MyVector MATERIAL_SPECULAR = MyVector.makeVector4(
			0.1f, 0.1f, 0.1f, 1.0f);

	/*
	 * List of 3D objects, organized in a scene graph (initialized with an empty
	 * dummy node).
	 */
	private Node rootNode = new Node();

	/**
	 * Camera object.
	 */
	private final Camera camera;

	/*
	 * Constructor.
	 */
	public Renderer3D(Camera c) {
		camera = c;
	}

	/*
	 * Initialize the scene graph.
	 */
	private void init() {
		rootNode.init();
	}

	/*
	 * Event-Handler: surface created. (non-Javadoc)
	 * 
	 * @see
	 * android.opengl.GLSurfaceView.Renderer#onSurfaceCreated(javax.microedition
	 * .khronos.opengles.GL10, javax.microedition.khronos.egl.EGLConfig)
	 */
	public void onSurfaceCreated(GL10 gl, EGLConfig config) {

		Log.i(LOG_TAG, "onSurfaceCreated()");
		gl.glMatrixMode(GL10.GL_PROJECTION);

		final int width = 320;
		final int height = 480;
		GLU.gluPerspective(gl, 45f, (float) width / (float) height, 0.01f, 200f);

		gl.glViewport(0, 0, (int) width, (int) height);
		gl.glMatrixMode(GL10.GL_MODELVIEW);
		gl.glEnable(GL10.GL_DEPTH_TEST);

		// preparation
		// enable the differentiation of which side may be visible
		gl.glEnable(GL10.GL_CULL_FACE);
		// which is the front? the one which is drawn counter clockwise
		gl.glFrontFace(GL10.GL_CW);
		// which one should NOT be drawn
		gl.glCullFace(GL10.GL_BACK);

		// preparation
		gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
		// gl.glEnableClientState(GL10.GL_COLOR_ARRAY);
		gl.glEnableClientState(GL10.GL_NORMAL_ARRAY);

		// define the color we want to be displayed as the "clipping wall"
		gl.glClearColor((float) BACKGROUND_COLOR.get(0),
				(float) BACKGROUND_COLOR.get(1),
				(float) BACKGROUND_COLOR.get(2),
				(float) BACKGROUND_COLOR.get(3));

		// Enable lighting
		gl.glEnable(GL10.GL_LIGHTING);

		// Set a light
		gl.glEnable(GL10.GL_LIGHT0);
		float lightPosition[] = new float[] { (float) LIGHT_POSITION.get(0),
				(float) LIGHT_POSITION.get(1), (float) LIGHT_POSITION.get(2),
				(float) LIGHT_POSITION.get(3) };
		float lightDiffuse[] = new float[] { (float) LIGHT_DIFFUSE.get(0),
				(float) LIGHT_DIFFUSE.get(1), (float) LIGHT_DIFFUSE.get(2),
				(float) LIGHT_DIFFUSE.get(3) };
		float lightAmbient[] = new float[] { (float) LIGHT_AMBIENT.get(0),
				(float) LIGHT_AMBIENT.get(1), (float) LIGHT_AMBIENT.get(2),
				(float) LIGHT_AMBIENT.get(3) };

		gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_POSITION, lightPosition, 0);
		gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_DIFFUSE, lightDiffuse, 0);
		gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_AMBIENT, lightAmbient, 0);

		// Set material
		float diffuseColor[] = new float[] { (float) MATERIAL_DIFFUSE.get(0),
				(float) MATERIAL_DIFFUSE.get(1),
				(float) MATERIAL_DIFFUSE.get(2),
				(float) MATERIAL_DIFFUSE.get(3) };
		gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_DIFFUSE, diffuseColor,
				0);
		float ambientColor[] = new float[] { (float) MATERIAL_AMBIENT.get(0),
				(float) MATERIAL_AMBIENT.get(1),
				(float) MATERIAL_AMBIENT.get(2),
				(float) MATERIAL_AMBIENT.get(3) };
		gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_AMBIENT, ambientColor,
				0);
		float specularColor[] = new float[] { (float) MATERIAL_SPECULAR.get(0),
				(float) MATERIAL_SPECULAR.get(1),
				(float) MATERIAL_SPECULAR.get(2),
				(float) MATERIAL_SPECULAR.get(3) };
		gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_SHININESS,
				specularColor, 0);

		// Shading
		gl.glShadeModel(GL10.GL_SMOOTH);

		gl.glEnable(GL10.GL_NORMALIZE);

		init();
	}

	/**
	 * Event-Handler: surface changed. (non-Javadoc)
	 * 
	 * @see android.opengl.GLSurfaceView.Renderer#onSurfaceChanged(javax.microedition.khronos.opengles.GL10,
	 *      int, int)
	 */
	public void onSurfaceChanged(GL10 gl, int w, int h) {
		gl.glViewport(0, 0, w, h);
		GLU.gluPerspective(gl, camera.getOpeningAngle(), (float) w / (float) h,
				camera.getNearPlane(), camera.getFarPlane());
	}

	/*
	 * Event-Handler: draw frame event. (non-Javadoc)
	 * 
	 * @see
	 * android.opengl.GLSurfaceView.Renderer#onDrawFrame(javax.microedition.
	 * khronos.opengles.GL10)
	 */
	public void onDrawFrame(GL10 gl) {
		// clear the color buffer and the depth buffer
		gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);

		updateGlView(gl);

		rootNode.draw(gl);
	}

	/**
	 * Update the GL matrix stack based on the camera.
	 * 
	 * @param gl
	 */
	public void updateGlView(GL10 gl) {
		gl.glLoadIdentity();
		final float eyeX = (float) camera.getEye().get(0);
		final float eyeY = (float) camera.getEye().get(1);
		final float eyeZ = (float) camera.getEye().get(2);
		final float refX = (float) camera.getRef().get(0);
		final float refY = (float) camera.getRef().get(1);
		final float refZ = (float) camera.getRef().get(2);
		final float upX = (float) camera.getUp().get(0);
		final float upY = (float) camera.getUp().get(1);
		final float upZ = (float) camera.getUp().get(2);
		GLU.gluLookAt(gl, eyeX, eyeY, eyeZ, refX, refY, refZ, upX, upY, upZ);
	}

	/**
	 * Getter.
	 * 
	 * @return
	 */
	public Camera getCamera() {
		return camera;
	}

	/**
	 * Setter.
	 * 
	 * @param scene
	 */
	public void setScene(Node scene) {
		rootNode = scene;
	}
}
